package cn.nawang.ebeim.client;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.nawang.ebeim.client.bean.BackupQueue;
import cn.nawang.ebeim.client.bean.DataSource;
import cn.nawang.ebeim.client.bean.DataSourceQueue;
import cn.nawang.ebeim.client.bean.DataSourceTask;
import cn.nawang.ebeim.client.bean.DataSourceVersion;
import cn.nawang.ebeim.client.bean.FileBackupTask;
import cn.nawang.ebeim.client.bean.TaskStatusBean;
import cn.nawang.ebeim.client.bean.UploadProcessBarBean;
import cn.nawang.ebeim.client.bean.VersionBackupTask;
import cn.nawang.ebeim.client.db.operate.MySqlImportAndExport;
import cn.nawang.ebeim.client.exception.ApiRequestException;
import cn.nawang.ebeim.client.util.ApiUtil;
import cn.nawang.ebeim.client.util.ApiVisitUtil;
import cn.nawang.ebeim.client.util.CommonMethodsUtils;
import cn.nawang.ebeim.client.util.Constants;
import cn.nawang.ebeim.client.util.DbUpDownloadUtil;
import cn.nawang.ebeim.client.util.DbUtil;
import cn.nawang.ebeim.client.util.FileUtil;
import cn.nawang.ebeim.client.util.PropertiesConstants;
import cn.nawang.ebeim.client.util.PropertiesUtil;
import cn.nawang.ebeim.client.util.RunningDsIds;
import cn.nawang.ebeim.client.version.FileNode;
import cn.nawang.ebeim.client.version.Node;
import cn.nawang.ebeim.client.version.VersionTree;
import cn.nawang.ebeim.client.version.VersionTreeUtil;

public class DataSourceThread implements Runnable {
  private static final Logger LOG = LoggerFactory.getLogger(DataSourceThread.class);

  @Override
  public void run() {
    LOG.info("start datasource backup thread at {}", System.currentTimeMillis());
    while (!AppMain.stopFlag.get()) {
      /* 从队列获取备份任务，执行备份 */
      DataSourceTask task;
      try {
        task = DataSourceQueue.dequeue();
        backupDataSource(task);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

    }

  }

  /** 备份数据源 */
  public void backupDataSource(DataSourceTask task) {

    if (task.getDataSource().getDsType().equals(Constants.DS_TYPE_DATABASE)) {
      long e1 = System.currentTimeMillis();
      try {
        int rs = new MySqlImportAndExport(task.getDataSource()).exportDb();
        if (rs != 0) {
          // 日志处理
          CommonMethodsUtils.backupLogProcess(task.getDataSource().getId(),
              task.getDataSource().getNextVersion().getMajorVersion(),
              task.getDataSource().getNextVersion().getMinorVersion(),
              Constants.BACKUPLOG_STATUS_FAILED, 0l, "您备份的数据库连接信息错误");

          DataSource datasource = new DataSource();
          int vMajor = task.getDataSource().getCurrentVersionMajor();
          int vMinor = task.getDataSource().getCurrentVersionMinor();
          int count = task.getDataSource().getCount();
          datasource.setId(task.getDataSource().getId());
          datasource.setCurrentVersionMajor(vMajor <= 0 ? 0 : (vMajor - 1));
          datasource.setCurrentVersionMinor(vMinor);
          datasource.setCount(count <= 0 ? 0 : (count - 1));
          datasource.setStatus(Constants.DS_RUN_STATUS_ERROR);
          ApiVisitUtil.updataDatasource(datasource);

          return;
        }
      } catch (Exception e) {
        e.printStackTrace();
        // 日志处理
        CommonMethodsUtils.backupLogProcess(task.getDataSource().getId(),
            task.getDataSource().getNextVersion().getMajorVersion(),
            task.getDataSource().getNextVersion().getMinorVersion(),
            Constants.BACKUPLOG_STATUS_FAILED, 0l, "您备份的数据库连接信息错误");
        return;
      }
      long e2 = System.currentTimeMillis();
      LOG.info("数据库导出使用时间：" + (e2 - e1) / 1000.0 + "s");
      task.getDataSource().setRoot(FileUtil.getDbUploadPath(task.getDataSource().getId(), ""));
    }

    DataSource dataSource = task.getDataSource();
    LOG.info("backup datasource: {}", dataSource);
    /** 获取版本号 */
    DataSourceVersion currentVersion = dataSource.getCurrentVersion();
    DataSourceVersion nextVersion = dataSource.getNextVersion();
    /** 获取最近的版本树 */
    VersionTree currentTree = null;
    if (!currentVersion.isInitial()) {
      currentTree = VersionTreeUtil.getVersionTree(dataSource.getId(), currentVersion.getVersion());
    }
    /** 生成最新版本树 */
    VersionTree nextTree = null;
    try {
      long e1 = System.currentTimeMillis();
      nextTree = generateVersionTree(dataSource);
      long e2 = System.currentTimeMillis();
      LOG.info("生成版本树使用时间：" + (e2 - e1) / 1000.0 + "s");
      // 添加删除节点的记录
      if (currentTree != null) {
        nextTree.mergeAll(currentTree);
        nextTree.visit();
        DbUtil.deleteVersion(dataSource.getId(), dataSource.getCurrentVersion().getVersion());
      }

      // nextTree.addToList(dataSource.getId(), dataSource.getNextVersion().getVersion());
      // DbUtil.addVersionTreeMany(nextTree.getBeans());

    } catch (IOException e) {
      LOG.error("can't generate version tree for datasource: {}", dataSource, e.getMessage());
      ApiUtil.updateDataSourceStatus(dataSource, Constants.DS_RUN_STATUS_ERROR);

      // 日志处理
      CommonMethodsUtils.backupLogProcess(dataSource.getId(),
          dataSource.getNextVersion().getMajorVersion(),
          dataSource.getNextVersion().getMinorVersion(), Constants.BACKUPLOG_STATUS_FAILED, 0l,
          "生成版本树失败");

      return;

    }

    /** 保存最新版本树 */
    String exist = VersionTreeUtil.getVersionTreePath(dataSource.getId(), nextVersion.getVersion());
    File vFile = new File(exist);
    if (!vFile.exists()) {
      VersionTreeUtil.save(nextTree, dataSource.getId(), nextVersion.getVersion());
    }
    addDbVersion(dataSource.getId(), nextVersion);

    /** 获取文件变更列表 */
    int flag = processChanges(dataSource, currentTree, nextTree);
    /** 将版本树加入备份队列 */
    enqueueBackup(nextVersion, dataSource);
    if (flag == 0) {
      /** 更新数据源备份状态为备份完成 */
      ApiUtil.updateDataSourceStatus(dataSource, Constants.DS_RUN_STATUS_SUCCESS);
      DbUtil.updateBackupTask(task.getId(), DbUtil.STATUS_FINISHED);

      // 日志处理
      CommonMethodsUtils.backupLogProcess(dataSource.getId(),
          dataSource.getNextVersion().getMajorVersion(),
          dataSource.getNextVersion().getMinorVersion(), Constants.BACKUPLOG_STATUS_SUCCESS, 0l,
          "备份成功!");
    }

  }

  // 暂时添加到sqlite中，上传成功后删除
  public void addDbVersion(String dsId, DataSourceVersion version) {
    TaskStatusBean taskStatus = new TaskStatusBean();
    taskStatus.setDsId(dsId);
    taskStatus.setVersion(version);
    taskStatus.setType(DbUtil.TASK_TYPE_VERSION);
    taskStatus.setStatus(DbUtil.TASK_STATUS_RUNNING);
    DbUtil.addTaskStatus(taskStatus);
  }

  // 生成TaskStatusBean对象
  public TaskStatusBean newTaskStatusBean(DataSource dataSource, FileNode fNode) {
    TaskStatusBean taskStatus = new TaskStatusBean();
    taskStatus.setDsId(dataSource.getId());
    taskStatus.setSignature(fNode.getInfo().getSignature());
    taskStatus.setType(DbUtil.TASK_TYPE_FILE);
    taskStatus.setStatus(DbUtil.TASK_STATUS_RUNNING);
    taskStatus.setDsPath(dataSource.getRoot());
    taskStatus.setInfoPath(fNode.getInfo().getPath());
    taskStatus.setInfoName(fNode.getInfo().getName());
    taskStatus.setFileSize(fNode.getInfo().getFileSize());
    taskStatus.setIsEncrypt(dataSource.getIsEncrypt());
    taskStatus.setVersion(dataSource.getNextVersion());
    String dsType = dataSource.getDsType();
    if (dsType == null) {
      taskStatus.setDsType(0);
    } else {
      taskStatus.setDsType(Integer.valueOf(dataSource.getDsType()));
    }
    return taskStatus;
  }

  /**
   * @author zhangjf
   * @Description: 新增上传进度条
   * @param @param nodeMap
   * @param @param dsId
   * @return void
   * @date 2015年10月14日上午10:13:43
   *
   * @修改人 zhangjf
   * @修改日期
   */
  public void addUploadProcessBar(Map<String, Node> nodeMap, DataSource ds, int initSize) {
    long total = 0l;
    if (nodeMap.size() > 0) {
      for (String key : nodeMap.keySet()) {
        FileNode fn = (FileNode) nodeMap.get(key);
        total += fn.getInfo().getFileSize();
      }
      UploadProcessBarBean bean = new UploadProcessBarBean();
      bean.setDsId(ds.getId());
      bean.setStatus(DbUpDownloadUtil.PROCESS_BAR_STATUS_RUNNING);
      bean.setTotal(total);
      bean.setCount(0l);
      bean.setFileSum(nodeMap.size() + initSize);
      bean.setOkSum(initSize);
      bean.setName(ds.getName());
      bean.setDsType(ds.getDsType());
      bean.setUserID(PropertiesUtil.read(PropertiesConstants.USER_ID));
      DbUpDownloadUtil.deleteUploadProcessBar(ds.getId());
      DbUpDownloadUtil.addUploadProcessBar(bean);
    }
  }

  /** 处理文件变更情况 */
  public int processChanges(DataSource dataSource, VersionTree currentTree, VersionTree nextTree) {

    long e5 = System.currentTimeMillis();
    List<Node> nodeList = VersionTreeUtil.getChangeList(currentTree, nextTree);
    Map<String, Node> nodeMap = new HashMap<String, Node>();
    int mapSize = 0;
    long e6 = System.currentTimeMillis();
    LOG.info("getChangeList使用时间：" + (e6 - e5) / 1000.0 + "s");
    List<TaskStatusBean> beans = new ArrayList<TaskStatusBean>();
    // 先将节点暂时持久化，上传成功后删除
    for (Node node : nodeList) {
      FileNode fNode = (FileNode) node;
      /** only files added or changed add to backup queue */
      if (fNode.getStatus() != Node.STATUS_ADD && fNode.getStatus() != Node.STATUS_CHANGE
          && fNode.getStatus() != Node.STATUS_NOOP) {
        continue;
      }
      if (nodeMap.get(fNode.getInfo().getSignature()) == null
          && fNode.getStatus() != Node.STATUS_FAILED) {
        beans.add(newTaskStatusBean(dataSource, fNode));
        nodeMap.put(fNode.getInfo().getSignature(), fNode);
      }
    }
    mapSize = nodeMap.size();
    long e7 = System.currentTimeMillis();
    LOG.info("遍历FileNodeMap使用时间：" + (e7 - e6) / 1000.0 + "s");
    // 暂时添加到sqlite中，上传成功后删除
    long e1 = System.currentTimeMillis();
    DbUtil.addTaskStatusMany(beans);
    long e2 = System.currentTimeMillis();
    LOG.info("待上传节点存入sqlite使用时间：" + (e2 - e1) / 1000.0 + "s");

    // 计算数据源总大小，并存入sqilte，以便计算上传的进度条
    int initSize = nodeList.size() - nodeMap.size();
    addUploadProcessBar(nodeMap, dataSource, initSize);

    // 逐个加入队列
    long e3 = System.currentTimeMillis();
    for (Node node : nodeList) {
      FileNode fNode = (FileNode) node;
      /** only files added or changed add to backup queue */
      if (fNode.getStatus() != Node.STATUS_ADD && fNode.getStatus() != Node.STATUS_CHANGE
          && fNode.getStatus() != Node.STATUS_NOOP) {
        /* 第一次备份时，currentTree为null, nextTree上所有节点都是变更节点,但状态都是STATUS_NOOP */
        if (fNode.getStatus() == Node.STATUS_DELETED) {
          try {
            ApiUtil.addFileChangeLog(fNode, dataSource.getId(),
                dataSource.getNextVersion().getVersion());
          } catch (ApiRequestException e) {
            // TODO:可能会造成数据丢失，导致单个文件下载不了
            LOG.error("fail to addFileChangeLog:{}", e);
            e.printStackTrace();
          }
        }
        continue;
      }
      FileBackupTask ft = new FileBackupTask();
      ft.setInfo(fNode.getInfo());
      ft.setDataSource(dataSource);
      ft.setIsEncrypt(dataSource.getIsEncrypt());

      try {
        ApiUtil.addFileChangeLog(fNode, dataSource.getId(),
            dataSource.getNextVersion().getVersion());
      } catch (ApiRequestException e8) {
        // TODO Auto-generated catch block
        LOG.error("fail to addFileChangeLog:{}", e8);
        e8.printStackTrace();
      }

      try {
        if (nodeMap.get(fNode.getInfo().getSignature()) != null
            && fNode.getStatus() != Node.STATUS_FAILED) {
          BackupQueue.enqueue(ft);
          nodeMap.remove(fNode.getInfo().getSignature());
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    // 将dsId添加到全局变量（必须保证待上传节点信息已经存入sqlite数据库才将dsid加入全局变量）
    RunningDsIds.addId(dataSource.getId());

    long e4 = System.currentTimeMillis();
    LOG.info("待上传文件节点放入map使用时间：" + (e4 - e3) / 1000.0 + "s");
    LOG.info("map长度:" + mapSize);
    return mapSize;
  }

  /** 生成数据源的当前版本树 */
  public VersionTree generateVersionTree(DataSource ds) throws IOException {
    LOG.debug("generateVersionTree");
    VersionTree vt = VersionTreeUtil.generateVersionTree(ds);
    return vt;
  }

  /** 将版本树加入到备份队列 */
  public void enqueueBackup(DataSourceVersion version, DataSource dataSource) {

    LOG.debug("enqueueBackup");
    VersionBackupTask task = new VersionBackupTask();
    task.setDataSource(dataSource);
    task.setVersion(version);
    task.setSrcPath(getVersionFilePath(dataSource, version));
    try {
      BackupQueue.enqueue(task);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  public String getVersionFilePath(DataSource ds, DataSourceVersion version) {
    return ds.getId() + "-" + version.getVersion();
  }

}
